package com.fs.controller.v2;

import com.fs.controller.vo.PageData;
import com.fs.controller.vo.friend.RespFriend;
import com.fs.controller.vo.purchase.*;
import com.fs.global.init.RoleName;
import com.fs.realm.UserContext;
import com.fs.schedule.ScheduleTool;
import com.fs.service.*;
import com.fs.service.bo.BoPageInfo;
import com.fs.service.bo.friend.BoFriend;
import com.fs.service.bo.price.BoPrice;
import com.fs.service.bo.purchase.*;
import com.fs.service.bo.room.BoRoom;
import com.fs.service.bo.wxpay.BoWxPayParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Api(tags = "交易订单", description = "小程序用户使用")
@Log4j2
@RestController
@RequestMapping("/v2/purchase")
@RequiresRoles(RoleName.CUSTOMER)
@Validated
public class GuestPurchaseController {

    private final PurchaseService purchaseService;
    private final PriceService priceService;
    private final RoomService roomService;
    private final WxPayService wxPayService;
    private final ScheduleTool scheduleTool;


    public GuestPurchaseController(PurchaseService purchaseService, PriceService priceService,
                                   RoomService roomService, WxPayService wxPayService, ScheduleTool scheduleTool) {
        this.purchaseService = purchaseService;
        this.priceService = priceService;
        this.roomService = roomService;
        this.wxPayService = wxPayService;
        this.scheduleTool = scheduleTool;
    }

    @ApiOperation("获取待使用订单详情")
    @GetMapping("/{id}")
    public RespPurchase getToBeUsedPurchase(@PathVariable Integer id) {
        BoPurchaseWithRoomAndCustomer purchase = purchaseService.getById(id);
        if (purchase.getState() != Status.ToBeUsed) {
            throw new PurchaseStateException("该订单不是待使用状态");
        }
        return new RespPurchase(purchase);
    }

    @ApiOperation("创建订单")
    @PostMapping
    public RespCreatePurchase createPurchase(@RequestBody @Valid ReqCreate req) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        LocalDateTime start = LocalDateTime.parse(req.getBeginDateTime(), formatter);
        LocalDateTime stop = LocalDateTime.parse(req.getEndDateTime(), formatter);

        if (!start.toLocalDate().equals(stop.toLocalDate())) {
            throw new BadRequestException("开始时间和结束时间不是同一天");
        }
        if (start.isAfter(stop)) {
            throw new BadRequestException("开始时间不能晚于结束时间");
        }
        if (stop.isBefore(LocalDateTime.now())) {
            throw new BadRequestException("预约时间不能早于当前时间");
        }

        int customerId = UserContext.getPrinciple();
        float amount = computePrice(req.getRoomID(), start.toLocalTime(), stop.toLocalTime());

        BoCreateParam param = new BoCreateParam().setRoomID(req.getRoomID()).setAmount(amount).setBeginTime(start)
                .setEndTime(stop).setCustomerID(customerId);
        BoPurchase purchase = purchaseService.createPurchase(param);
        return new RespCreatePurchase(purchase);
    }

    @ApiOperation("计算价格")
    @PostMapping("/calculate")
    @RequiresRoles(value = {RoleName.CUSTOMER, RoleName.GUEST}, logical = Logical.OR)
    public float calculatePrice(@RequestBody @Valid ReqCalculatePrice req) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        LocalDateTime start = LocalDateTime.parse(req.getBeginTime(), formatter);
        LocalDateTime stop = LocalDateTime.parse(req.getEndTime(), formatter);

        if (!start.toLocalDate().equals(stop.toLocalDate())) {
            throw new BadRequestException("开始时间和结束时间不是同一天");
        }
        if (start.isAfter(stop)) {
            throw new BadRequestException("开始时间不能晚于结束时间");
        }
        if (stop.isBefore(LocalDateTime.now())) {
            throw new BadRequestException("预约时间不能早于当前时间");
        }
        return computePrice(req.getRoomId(), start.toLocalTime(), stop.toLocalTime());
    }

    @ApiOperation("发起支付")
    @PostMapping("/pay")
    public Map<String, String> startPay(@RequestParam Integer purchaseId) {
        BoPurchaseWithRoomAndCustomer bo = purchaseService.getById(purchaseId);

        synchronized (this) {
            List<BoRoomBookedTime> bookedTimes = purchaseService.getOccupiedTimeOfByRoomAndDate(bo.getRoom().getId(), bo.getBeginTime());
            //判断订单时间是否和已预订或正支付的订单有交集
            boolean isTimeCrash = bookedTimes.stream().anyMatch(t -> {
                LocalDateTime _start = t.getStart();
                LocalDateTime _end = t.getEnd();
                if (!bo.getEndTime().isAfter(_start)) {
                    return false;
                }
                if (!bo.getBeginTime().isBefore(_end)) {
                    return false;
                }
                return true;
            });

            if (isTimeCrash) {
                throw new TimeCrashException();
            } else {
                purchaseService.lockPurchase(purchaseId);
            }
        }

        String description = "共享茶室" + bo.getRoom().getName() + "消费";

        BoWxPayParam boWxPay = new BoWxPayParam(bo.getCustomer().getWxID(), bo.getAmount(), bo.getTradeNo(), description);
//        BoWxPayParam boWxPay = new BoWxPayParam(bo.getCustomer().getWxID(), 0.01f, bo.getTradeNo(), description);
        try {
            Map<String, String> result = wxPayService.requestPrepayId(boWxPay);
            //310秒订单关闭时间
            Date expiredTime = new Date(new Date().getTime() + 310000);
            scheduleTool.addCloseWxOrderTask(bo.getTradeNo(), expiredTime);
            return result;
        } catch (WxPayService.RequestWxApiException e) {
            throw new WechatApiException(e.getMessage());
        }
    }

    @ApiOperation("获取某用户所有历史订单")
    @GetMapping("/all")
    public PageData<RespQueryPurchase> getAllPurchaseOfCustomer(@Valid ReqQueryPurchase req) {
        int customerId = UserContext.getPrinciple();
        BoQueryParam param = BoQueryParam.builder().customerId(customerId).build();
        if (req.getState() != null) {
            Optional<Status> state = Arrays.stream(Status.values())
                    .filter(s -> s.name().equals(req.getState()))
                    .findFirst();
            if (!state.isPresent()) {
                throw new BadRequestException("状态参数非法");
            }
            param.setState(state.get());
        }

        BoPageInfo<BoPurchaseWithRoomAndCustomer> result = purchaseService.query(param, req.getPageNo(), req.getPageSize());
        List<RespQueryPurchase> data = result.getResults().stream().map(RespQueryPurchase::new).collect(Collectors.toList());
        return new PageData<>(data, result.getTotal());
    }

    @ApiOperation("获取某订单的被邀请的朋友")
    @GetMapping("/friends")
    public List<RespFriend> getFriendsOfPurchase(@RequestParam Integer purchaseId) {
        List<BoFriend> friends = purchaseService.getFriendsOfPurchase(purchaseId);
        return friends.stream().map(RespFriend::new).collect(Collectors.toList());
    }

    @ApiOperation("邀请朋友")
    @PutMapping("/invite")
    public void inviteFriend(@RequestBody @Valid ReqInviteFriend req) {

        purchaseService.inviteFriend(req.getPurchaseId(), req.getFriendIds());
    }

    @ApiOperation("取消邀请朋友")
    @PutMapping("/invite/cancel")
    public void cancelInviteFriend(@RequestBody @Valid ReqCancelInvite req) {
        purchaseService.cancelInviteFriend(req.getPurchaseId(), req.getFriendIds());
    }


    /**
     * 计算价格
     *
     * @param roomID 房间id
     * @param start  起始时间
     * @param end    结束时间
     * @return 房间价格 ,单位:元
     */
    public float computePrice(int roomID, LocalTime start, LocalTime end) {
        Duration du = Duration.between(start, end);
        long minutes = du.toMinutes();

        List<BoPrice> pricesOfRoom = priceService.getPricesOfRoom(roomID);
        BoRoom room = roomService.getByID(roomID);
        if (pricesOfRoom.size() == 0) {
            return room.getPrice() * minutes;
        }

        //用户预定时间在某个自定义计价区间内部
        List<BoPrice> newPrices = pricesOfRoom.stream().filter(p -> {
            return (p.getBeginTime().equals(start) || p.getBeginTime().isBefore(start))
                    && (p.getEndTime().equals(end) || p.getEndTime().isAfter(end));
        }).collect(Collectors.toList());

        if (newPrices.size() > 0) {
            if (newPrices.size() > 1) {
                log.warn("价格区间有重复,roomID:" + roomID);
            }
            return newPrices.get(0).getPrice() * minutes;
        }
        //查找用户预定时间是否与自定义计价区间交错
        BoPrice headPeriod = null;
        BoPrice footPeriod = null;
        List<BoPrice> bodyPeriods = new ArrayList<>();

        for (BoPrice p : pricesOfRoom) {
            LocalTime customStart = p.getBeginTime();
            LocalTime customEnd = p.getEndTime();

            if (headPeriod == null) {
                if ((customStart.isBefore(start) || customStart.equals(start))
                        && (customEnd.isAfter(start) && customEnd.isBefore(end)))
                    headPeriod = p;
            }
            if (footPeriod == null) {
                if ((customEnd.isAfter(end) || customEnd.equals(end))
                        && (customStart.isAfter(start) && customStart.isBefore(end)))
                    footPeriod = p;
            }
            if (customStart.isAfter(start) && customEnd.isBefore(end)) {
                bodyPeriods.add(p);
            }
        }

        //用户预定时间与自定义计价区间没有任何交错
        if (headPeriod == null && footPeriod == null && bodyPeriods.size() == 0) {
            return room.getPrice() * minutes;
        }

        //用户预定时间内部包含的自定义计价区间
        if (bodyPeriods.size() > 0) {
            bodyPeriods = bodyPeriods.stream().sorted((p1, p2) -> {
                if (p1.getBeginTime().isBefore(p2.getBeginTime())) {
                    return -1;
                } else if (p1.getBeginTime().equals(p2.getBeginTime())) {
                    return 0;
                } else {
                    return 1;
                }
            }).collect(Collectors.toList());
            float bodyPrice = calculateBodyPrice(bodyPeriods, room.getPrice());

            float headPrice;
            if (headPeriod != null) {
                float headPrice_1 = headPeriod.getPrice() * Duration.between(start, headPeriod.getEndTime()).toMinutes();
                float headPrice_2 = room.getPrice() * Duration.between(headPeriod.getEndTime(), bodyPeriods.get(0).getBeginTime()).toMinutes();
                headPrice = headPrice_1 + headPrice_2;
            } else {
                headPrice = room.getPrice() * Duration.between(start, bodyPeriods.get(0).getBeginTime()).toMinutes();
            }

            float footPrice;
            if (footPeriod != null) {
                float footPrice_1 = footPeriod.getPrice() * Duration.between(footPeriod.getBeginTime(), end).toMinutes();
                float footPrice_2 = room.getPrice() * Duration.between(bodyPeriods.get(bodyPeriods.size() - 1).getEndTime(), footPeriod.getBeginTime()).toMinutes();
                footPrice = footPrice_1 + footPrice_2;
            } else {
                footPrice = room.getPrice() * Duration.between(bodyPeriods.get(bodyPeriods.size() - 1).getEndTime(), end).toMinutes();
            }
            return bodyPrice + headPrice + footPrice;

        } else {
            if (headPeriod != null && footPeriod == null) {
                float price_1 = headPeriod.getPrice() * Duration.between(start, headPeriod.getEndTime()).toMinutes();
                float price_2 = room.getPrice() * Duration.between(headPeriod.getEndTime(), end).toMinutes();
                return price_1 + price_2;
            }
            if (headPeriod == null && footPeriod != null) {
                float price_1 = room.getPrice() * Duration.between(start, footPeriod.getBeginTime()).toMinutes();
                float price_2 = footPeriod.getPrice() * Duration.between(footPeriod.getBeginTime(), end).toMinutes();
                return price_1 + price_2;
            }
            if (headPeriod != null && footPeriod != null) {
                float price_1 = headPeriod.getPrice() * Duration.between(start, headPeriod.getEndTime()).toMinutes();
                float price_2 = room.getPrice() * Duration.between(headPeriod.getEndTime(), footPeriod.getBeginTime()).toMinutes();
                float price_3 = footPeriod.getPrice() * Duration.between(footPeriod.getBeginTime(), end).toMinutes();
                return price_1 + price_2 + price_3;
            }
        }
        throw new RuntimeException("不可能到这里");
    }

    private float calculateBodyPrice(List<BoPrice> bodyPrices, float normalUnitPrice) {
        if (bodyPrices.size() == 1) {
            long minutes = Duration.between(bodyPrices.get(0).getBeginTime(), bodyPrices.get(0).getEndTime()).toMinutes();
            return bodyPrices.get(0).getPrice() * minutes;
        }

        float totalPrice = 0;
        int i = 0;
        for (i = 0; i < bodyPrices.size() - 1; i++) {
            BoPrice p1 = bodyPrices.get(i);
            BoPrice p2 = bodyPrices.get(i + 1);
            float customPrice = p1.getPrice() * Duration.between(p1.getBeginTime(), p1.getEndTime()).toMinutes();
            float normalPrice = normalUnitPrice * Duration.between(p1.getEndTime(), p2.getBeginTime()).toMinutes();
            totalPrice += customPrice + normalPrice;
        }
        BoPrice last = bodyPrices.get(i);
        float lastCustomPrice = last.getPrice() * Duration.between(last.getBeginTime(), last.getEndTime()).toMinutes();
        totalPrice += lastCustomPrice;
        return totalPrice;
    }


}
